using FileTransfer.Client.Http.Model;
using FileTransfer.Client.Utils;
using Microsoft.Extensions.Logging;

namespace FileTransfer.Client.Upload;

public class FileUpload(ApiClient api, int chunkSize, ILoggerFactory? loggerFactory = null)
{
    private readonly ILogger<FileUpload>? _logger = loggerFactory?.CreateLogger<FileUpload>();
    public delegate void ChunkProgressHandler(ProgressInfo progressInfo);

    // Upload file from a stream provider function
    public async Task<string> UploadFileAsync(
        string fileName,
        Func<Stream> getFile,
        ChunkProgressHandler? onProgress = null
    )
    {
        var helper = new FileChunkPacker(fileName, getFile, chunkSize);
        var fullFileMd5 = helper.FileMd5;

        if (string.IsNullOrEmpty(fullFileMd5))
        {
            throw new ArgumentException("File MD5 calculation failed, MD5 value is empty!");
        }

        _logger?.LogInformation(
            "文件名: {FileName}, 文件大小: {FileSize}, 文件 MD5: {FileMD5}",
            helper.FileName,
            StringFormat.FormatSize(helper.TotalSize),
            fullFileMd5
        );

        var uploader = new FileChunkUploader(
            api,
            helper.FileName,
            fullFileMd5,
            helper.TotalSize,
            loggerFactory
        );
        var status = new ProgressStatus(helper.GetChunkCount(), helper.TotalSize);

        using var iterator = helper.Prepare();
        // Check for existing upload chunks
        List<ChunkInfo>? chunks = null;
        if (await uploader.FileApi.CheckUploadExistsAsync(fullFileMd5))
        {
            _logger?.LogInformation("文件记录已存在，准备断点续传: {FileName}", fileName);
            // Get information about already uploaded chunks
            chunks = (await uploader.FileApi.GetAllChunksAsync(fullFileMd5)).ToList();
        }
        else
        {
            _logger?.LogInformation("无记录，准备上传: {FileName}", fileName);
            await uploader.StartAsync();
        }

        while (iterator.MoveNext())
        {
            var chunk = iterator.Current;
            status.WorkingChunkBytesWritten = 0;
            status.WorkingChunkSize = chunk.ChunkData.Length;

            try
            {
                if (chunks != null)
                {
                    // Look for the current chunk in the upload records
                    var chunkInfo = chunks.FirstOrDefault(c => c.ChunkIndex == chunk.ChunkIndex);

                    if (chunkInfo != null)
                    {
                        // Check if MD5 matches the historical record
                        var historyChunkMd5 = chunkInfo.ChunkMD5;

                        if (historyChunkMd5 == chunk.ChunkMd5)
                        {
                            // MD5 matches, skip upload
                            _logger?.LogInformation(
                                "分片 [{ChunkIndex}] 已上传，跳过",
                                chunk.ChunkIndex
                            );
                            status.TotalBytesWritten += chunk.ChunkData.Length;
                            status.WorkingChunkBytesWritten = chunk.ChunkData.Length;
                            onProgress?.Invoke(status.GetProgressInfo());
                            continue; // Skip this chunk
                        }
                        else
                        {
                            // MD5 doesn't match, re-upload
                            _logger?.LogInformation(
                                "分片 [{ChunkIndex}] 已上传，但 MD5 不匹配，重新上传",
                                chunk.ChunkIndex
                            );
                        }
                    }
                }

                _logger?.LogInformation(
                    "开始上传分片: {ChunkIndex}, 起始位置：{ChunkOffset}，大小：{ChunkSize}，分片 MD5：{ChunkMD5}",
                    chunk.ChunkIndex,
                    StringFormat.FormatSize(chunk.ChunkOffset),
                    StringFormat.FormatSize(chunk.ChunkData.Length),
                    chunk.ChunkMd5
                );

                var success = await uploader.UploadFileChunkAsync(
                    chunk,
                    (bytesWritten, contentLength) =>
                    {
                        status.WorkingChunkBytesWritten = bytesWritten;
                        status.WorkingChunkSize = contentLength;
                        onProgress?.Invoke(status.GetProgressInfo());
                    }
                );

                if (!success)
                {
                    _logger?.LogError("上传失败，跳过分片: {ChunkIndex}", chunk.ChunkIndex);
                }
                else
                {
                    status.TotalBytesWritten += chunk.ChunkData.Length;
                    status.WorkingChunkBytesWritten = 0;
                    _logger?.LogInformation("分片 [{ChunkIndex}] 上传成功", chunk.ChunkIndex);
                }
            }
            finally
            {
                status.CurrentChunkIndex++;
                onProgress?.Invoke(status.GetProgressInfo());
            }
        }

        _logger?.LogInformation("文件上传完成: {FileName}", fileName);
        return await uploader.CompleteAsync();
    }

    // Upload file from a FileInfo object
    public async Task<string> UploadFileAsync(
        FileInfo file,
        ChunkProgressHandler? onProgress = null
    )
    {
        if (!file.Exists)
        {
            throw new ArgumentException($"Invalid file path: {file.FullName}");
        }

        return await UploadFileAsync(file.Name, () => file.OpenRead(), onProgress);
    }
}
